package com.b2wx.appcenter.plugin;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.StreamUtils;

import com.b2wx.appcenter.util.common.HttpUtil;
import com.b2wx.appcenter.util.common.JedisUtil;
import com.fasterxml.jackson.databind.ObjectMapper;

import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

public class PluginClient extends Thread{
	public static Map<String,PluginClient> pluginClientMap = new ConcurrentHashMap<>();
	private Map<String,HttpRequestListener> httpRequestListeners = new ConcurrentHashMap<>();
	private ObjectOutputStream output;
	private ObjectInputStream input;
	private String pluginId;
	private boolean failed=false;
	private Socket socket;
	private Map<String,Object> config;
	private List<SocketListener> socketListeners = new ArrayList<>();
	private Timer heartbeatTimer = new Timer();
	private String clientKey = null;

	
	public String getClientKey() {
		return clientKey;
	}

	@SuppressWarnings("unchecked")
	public PluginClient(Socket socket) {
		
		this.socket = socket;
		
		try {
			input = new ObjectInputStream(socket.getInputStream());
			output = new ObjectOutputStream(socket.getOutputStream	());
			Object readObject = input.readObject();
			if(!(readObject instanceof Map)) {
				send(Map.of("success",false,"message","第一次通讯必须传入配置信息"));
				failed = true;
				return;
			}
			Map<String,Object> params = (Map<String,Object>)readObject;
			pluginId= (String)params.get("pluginId");
			this.clientKey = pluginId+socket.getRemoteSocketAddress();
			config = (Map<String,Object>)params.get("config");
			if(pluginId==null) {
				send(Map.of("success",false,"message","未传入pluginId"));
				failed = true;
				return;
			}
			if(config==null||config.isEmpty()) {
				send(Map.of("success",false,"message","未传入配置信息"));
				failed = true;
				return;
			}
			HttpUtil.defaultClient().getAsString("https://appcenter.b2wx.com/plugin/api/plugin.status.update?id="+pluginId+"&activity=1");
			HttpUtil.defaultClient().postAsString("https://appcenter.b2wx.com/plugin/api/plugin.config.update?id="+pluginId, "application/x-www-form-urlencoded;charset=utf-8", Map.of("config",new ObjectMapper().writeValueAsString(config)));
			if(pluginClientMap.containsKey(clientKey)) {
				pluginClientMap.get(this.clientKey).close();
			}
			pluginClientMap.put(this.clientKey, this);
			send(Map.of("success",true));
			System.out.println("插件"+this.clientKey+"已接入服务");
			
			return;
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		
		String errorMessage = null;
		try {
			if(failed) {
				return;
			}
			socket.setSoTimeout(60000);
			for(SocketListener sl : socketListeners) {
				if(pluginId!=null&&config!=null) {
					sl.onOpen(pluginId, config);
				}
			}
			heartbeatTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					if(!socket.isClosed()) {
						try {
							send("heartbeat");
						} catch (IOException e) {
							e.printStackTrace();
						}
					}else {
						heartbeatTimer.cancel();
					}
				}
			}, 10000, 10000);
			Object obj = null;
			while((obj=input.readObject())!=null) {
				if(obj instanceof String) {
					if("heartbeat".equals(obj)) {
						continue;
					}
				}
				@SuppressWarnings("unchecked")
				Map<String,Object> params = (Map<String,Object>)obj;
				String command = (String)params.get("command");
				if("response".equals(command)) {
					String requestId= (String)params.get("requestId");
					@SuppressWarnings("unchecked")
					Map<String,String[]> headers = (Map<String,String[]>)params.get("headers");
					int status = (int)params.get("status");
					byte[] data = (byte[])params.get("data");
					if(httpRequestListeners.containsKey(requestId)) {
						httpRequestListeners.get(requestId).onResponse(status,headers, data);
						httpRequestListeners.remove(requestId);
					}
				}
				
				if("close".equals(command)) {
					send(null);
					break;
				}
			}
		} catch(SocketTimeoutException socketTimeoutException) {
			errorMessage = socketTimeoutException.getMessage();
			httpRequestListeners.forEach((k,v)->{
				v.onResponse(503, null, "插件服务器繁忙，请稍后再试".getBytes());
			});
			httpRequestListeners.clear();
			try {
				send(null);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}catch (IOException | ClassNotFoundException e) {
			errorMessage = e.getMessage();
		}finally {
			if(pluginId!=null) {
				if(pluginClientMap.containsKey(clientKey)) {
					pluginClientMap.remove(clientKey);
				}
				for(SocketListener sl : socketListeners) {
					sl.onClose(pluginId, config);
				}
				socketListeners.clear();
				try {
					JedisUtil.build().doAfterClose(jedis->{
						ScanResult<Entry<String, String>> scanResults = jedis.hscan("plugin.mapping", "0", new ScanParams().match(pluginId+"/*"));
						if(scanResults.getResult().isEmpty()) {
							HttpUtil.defaultClient().getAsString("https://appcenter.b2wx.com/plugin/api/plugin.status.update?id="+pluginId+"&activity=0");
						}
					});
				} catch (IOException e) {
					e.printStackTrace();
				}
				System.out.println("插件"+clientKey+"已断开服务"+(errorMessage==null?"":" - 异常:"+errorMessage));
			}
			if(!this.socket.isClosed()) {
				try {
					this.socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
		}
	}

	public void sendRequest(HttpServletRequest request,HttpServletResponse response) throws IOException, InterruptedException {
		String requestId = UUID.randomUUID().toString();
		Map<String,String[]> headers = new HashMap<>();
		Iterator<String> headerNames = request.getHeaderNames().asIterator();
		while(headerNames.hasNext()) {
			String headerName = headerNames.next();
			List<String> headerValueList = new ArrayList<>();
			Iterator<String> headerValues = request.getHeaders(headerName).asIterator();
			while(headerValues.hasNext()) {
				headerValueList.add(headerValues.next());
			}
			headers.put(headerName,headerValueList.toArray(new String[headerValueList.size()]));
		}
		Map<String,String[]> parameterMap = new HashMap<>();
		for(Map.Entry<String, String[]> entry:request.getParameterMap().entrySet()) {
			parameterMap.put(entry.getKey(), entry.getValue());
		}
		byte[] data = StreamUtils.copyToByteArray(request.getInputStream());
		Map<String,Object> map = new HashMap<>();
		map.put("uri", request.getRequestURI());
		map.put("query", request.getQueryString());
		map.put("method", request.getMethod());
		map.put("headers", headers);
		map.put("parameters", parameterMap);
		map.put("data", data);
		map.put("contentType", request.getContentType());
		map.put("requestId", requestId);
		map.put("pluginId", pluginId);
		
		httpRequestListeners.put(requestId, new HttpRequestListener() {
			@Override
			public void onResponse(int status,Map<String, String[]> headers, byte[] data) {
				try {
					synchronized (response){
						if(headers!=null) {
							for(Map.Entry<String, String[]> header : headers.entrySet()) {
								String[] headerValues = header.getValue();
								for(String headerValue:headerValues) {
									response.addHeader(header.getKey(), headerValue);
								}
							}
						}
						response.setStatus(status);
						response.getOutputStream().write(data);
						response.notify();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
		send(map);
		synchronized (response) {
			response.wait(60000);
			if(httpRequestListeners.containsKey(requestId)) {
				httpRequestListeners.remove(requestId);
				response.setStatus(503);
				response.setCharacterEncoding("UTF-8");
				response.getWriter().println("插件主机繁忙，请稍后再试");
			}
		}
	}
	
	public Socket getSocket() {
		return socket;
	}
	public synchronized void close() throws IOException {
		send(null);
		socket.close();
	}
	public synchronized void send(Object data) throws IOException {
		output.writeObject(data);
		output.flush();
	}
	public static interface HttpRequestListener{
		public void onResponse(int status,Map<String,String[]> headers,byte[] data);
	}
	public Map<String, Object> getConfig() {
		return config;
	}
	public void addSocketListener(SocketListener socketListener) {
		this.socketListeners.add(socketListener);
	}
	public static abstract class SocketListener{
		public void onOpen(String pluginId,Map<String,Object> config) {};
		public void onClose(String pluginId,Map<String,Object> config) {};
	}
}
